package com.haiyou.data.common.persister.mysql.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 索引
 * 
 * @author Administrator
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
@Documented
public @interface Index {// 索引

	/** 索引名称 */
	String name();

	/** 成员字段 ,多个表示联合索引 */
	String[] fields();

	/** 索引策略,默认是普通索引 */
	IndexPolicy policy() default IndexPolicy.NORMAL;

	/**
	 * 索引策略
	 * 
	 * @author Administrator
	 *
	 */
	enum IndexPolicy {

		UNIQUE("Unique", "BTREE") {
			@Override
			public String alterSQL(String db, String tab, String name, String... columns) {
				return String.format("alter table  `%s`.`%s` add %s index `%s` (%s) using %s", db, tab, type, name,
						multiColumn(columns), way);
			}

			@Override
			public String dropSQL(String db, String tab, String name) {
				return String.format("alter table `%s`.`%s` drop index `%s`", db, tab, name);
			}
		},

		/**
		 * <pre>
		使用全文索引的格式：  MATCH (columnName) AGAINST ('string')
		 eg:
		       SELECT * FROM `student` WHERE MATCH(`name`) AGAINST('聪')
		       当查询多列数据时：
		            建议在此多列数据上创建一个联合的全文索引，否则使用不了索引的。
		      		SELECT * FROM `student` WHERE MATCH(`name`,`address`) AGAINST('聪 广东')
		 * </pre>
		 * 
		 * <pre>
		MySQL 中的全文索引，有两个变量，最小搜索长度和最大搜索长度，对于长度小于最小搜索长度和大于最大搜索长度的词语，都不会被索引。通俗点就是说，想对一个词语使用全文索引搜索，那么这个词语的长度必须在以上两个变量的区间内。
		这两个的默认值可以使用以下命令查看
		show variables like '%ft%';
		可以看到这两个变量在 MyISAM 和 InnoDB 两种存储引擎下的变量名和默认值
		// MyISAM
		ft_min_word_len = 4;
		ft_max_word_len = 84;
		// InnoDB
		innodb_ft_min_token_size = 3;
		innodb_ft_max_token_size = 84;
		可以看到最小搜索长度 MyISAM 引擎下默认是 4，InnoDB 引擎下是 3，也即，MySQL 的全文索引只会对长度大于等于 4 或者 3 的词语建立索引，而刚刚搜索的只有 aaaa 的长度大于等于 4。
		配置最小搜索长度
		全文索引的相关参数都无法进行动态修改，必须通过修改 MySQL 的配置文件来完成。修改最小搜索长度的值为 1，首先打开 MySQL 的配置文件 /etc/my.cnf，在 [mysqld] 的下面追加以下内容
		[mysqld]
		innodb_ft_min_token_size = 1
		ft_min_word_len = 1
		然后重启 MySQL 服务器，并修复全文索引。注意，修改完参数以后，一定要修复下索引，不然参数不会生效。
		两种修复方式，可以使用下面的命令修复
		repair table test quick;
		或者直接删掉重新建立索引，再次执行上面的查询，a、aa、aaa 就都可以查出来了。
		但是，这里还有一个问题，搜索关键字 a 时，为什么 aa、aaa、aaaa 没有出现结果中，讲这个问题之前，先说说两种全文索引。
		两种全文索引
		自然语言的全文索引
		默认情况下，或者使用 in natural language mode 修饰符时，match() 函数对文本集合执行自然语言搜索，上面的例子都是自然语言的全文索引。
		自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里，相关度是基于匹配的关键词的个数，以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语，匹配时的相关度就越高。相反，非常常见的单词将不会被搜索，如果一个词语的在超过 50% 的记录中都出现了，那么自然语言的搜索将不会搜索这类词语。上面提到的，测试表中必须有 4 条以上的记录，就是这个原因。
		这个机制也比较好理解，比如说，一个数据表存储的是一篇篇的文章，文章中的常见词、语气词等等，出现的肯定比较多，搜索这些词语就没什么意义了，需要搜索的是那些文章中有特殊意义的词，这样才能把文章区分开。
		布尔全文索引
		在布尔搜索中，我们可以在查询中自定义某个被搜索的词语的相关性，当编写一个布尔搜索查询时，可以通过一些前缀修饰符来定制搜索。
		MySQL 内置的修饰符，上面查询最小搜索长度时，搜索结果 ft_boolean_syntax 变量的值就是内置的修饰符，下面简单解释几个，更多修饰符的作用可以查手册
		+ 必须包含该词
		- 必须不包含该词
		> 提高该词的相关性，查询的结果靠前
		< 降低该词的相关性，查询的结果靠后
		(*)星号 通配符，只能接在词后面
		对于上面提到的问题，可以使用布尔全文索引查询来解决，使用下面的命令，a、aa、aaa、aaaa 就都被查询出来了。
		select * table where match(content) against('a*' in boolean mode);
		 * </pre>
		 */
		FULLTEXT("FullText") {
			@Override
			public String alterSQL(String db, String tab, String name, String... columns) {
				return String.format("alter table `%s`.`%s` add %s index `%s`  (%s)", db, tab, type, name,
						multiColumn(columns));
			}

			@Override
			public String dropSQL(String db, String tab, String name) {
				return String.format("drop index `%s` on `%s`.`%s`", name, db, tab);
			}

		},

		NORMAL("Normal", "BTREE") {
			@Override
			public String alterSQL(String db, String tab, String name, String... columns) {
				return String.format("alter table `%s`.`%s` add index `%s` (%s) using %s", db, tab, name,
						multiColumn(columns), way);
			}

			@Override
			public String dropSQL(String db, String tab, String name) {
				return String.format("alter table `%s`.`%s` drop index `%s`", db, tab, name);
			}

		},

		;

		public final String type;
		public final String way;

		private IndexPolicy(String type, String way) {
			this.type = type;
			this.way = way;
		}

		private IndexPolicy(String type) {
			this(type, "");
		}

		public String alterSQL(String db, String tab, String name, String... columns) {
			throw new RuntimeException();
		}

		public String dropSQL(String db, String tab, String name) {
			throw new RuntimeException();
		}

		public static String multiColumn(String... columns) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < columns.length; i++) {
				sb.append('`').append(columns[i]).append('`');
				if (i < columns.length - 1)
					sb.append(',');
			}
			return sb.toString();
		}

	}

}
